<?php
namespace Snitso\IO\Reactor\Swoole\Multiple;

/**
 * Note: 多线程/多进程 Reactor 模型
 * Reactor => epoll
 *
 * @author:  Zhao Zhiqiang
 * @since:   2021/5/2 11:12
 * @version: 1.0
 *
 * @package Snitso\IO\Reactor\Swoole\Multiple
 */
class Worker
{
    /**
     * @var resource $socket
     */
    protected $socket;

    /**
     * @var string local socket
     */
    protected $localSocket;

    /**
     * @var \Closure
     */
    public $onConnect;

    /**
     * @var \Closure
     */
    public $onReceive;

    protected $workerNum = 4;

    protected $pidFile = 'pid_file';

    protected $masterFile = 'master_file';

    protected $pidArr = [];

    public function __construct(string $localSocket)
    {
        $this->localSocket = $localSocket;
    }

    protected function accept()
    {
        $context = stream_context_create([
            'socket' => [
                'backlog' => 102400     // 设置等待资源的个数
            ]
        ]);

        // 设置选项-端口重复使用
        stream_context_set_option($context, 'socket', 'so_reuseport', 1);

        $socket = stream_socket_server(
            $this->localSocket,
            $errno,
            $errstr,
            STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
            $context
        );
        if (!$socket) {
            echo $errstr . PHP_EOL;

            exit;
        }

        \Swoole\Event::add($socket, $this->createSocket());
    }

    protected function fork($workerNum = 0)
    {
        $workerNum = $workerNum ?: $this->workerNum;

        for ($i = 0; $i < $workerNum; $i++) {
            $sonPid = pcntl_fork();

            if ($sonPid === -1) {
                exit('fork 子进程错误' . PHP_EOL);
            } elseif ($sonPid === 0) {
                $this->debug('子进程ID：'. posix_getpid());
                $this->accept();
                exit;
            } else {
                $this->debug('父进程ID：'. posix_getpid());
                // 父进程空间
                $this->pidArr[] = $sonPid;

                $this->pidPut($this->pidFile, $sonPid);
            }
        }

        for ($i=0; $i < $this->workerNum; $i++) {
            $status = 0;
            $son = pcntl_wait($status);
            $this->debug('子进程: ' . $son); // 阻塞
        }
    }

    protected function createSocket()
    {
        return function ($socket) {
            $conn = stream_socket_accept($socket);

            if ($conn === false) {
                echo '连接失败' . PHP_EOL;
                exit;
            }

            ($this->onConnect)($this, $conn);

            \Swoole\Event::add($conn, $this->sendClient());
        };
    }

    protected function sendClient()
    {
        return function ($conn) {
            $data = fread($conn, 1024);

            $this->debug('正在使用的进程ID：'. posix_getpid());

            if (! $data || ! trim($data) || feof($conn)) {
                swoole_event_del($conn);
                fclose($conn);

                return;
            }

            ($this->onReceive)($this, $conn, $data);
        };
    }

    public function start()
    {
        $this->pidPut($this->pidFile);

        $this->pidPut($this->masterFile);
        $this->pidPut($this->masterFile, posix_getpid());

        $this->fork();

        // 答案是后
        $this->monitorWorkersForLinux();
    }

    public function reload()
    {
        $this->stop();

        $this->pidPut();

        $this->fork();
    }

    /**
     * Note: 停止子进程
     *
     * @author: Zhao Zhiqiang
     * @since: 2021/5/3 17:18
     */
    public function stop()
    {
        $this->debug('父进程：', $this->pidGet($this->masterFile));
        $this->debug('所有子进程：', $this->pidGet($this->pidFile));
        foreach ($this->pidGet($this->pidFile) as $pid) {
            posix_kill($pid, 9);
        }

        posix_kill($this->pidGet($this->masterFile)[0], 9);
    }

    /**
     * Note: 将子进程 PID 存放到文件
     *
     * @param $file
     * @param null $pid
     * @return bool|int
     * @author: Zhao Zhiqiang
     * @since: 2021/5/3 17:12
     */
    protected function pidPut($file, $pid = null)
    {
        return $pid === null
            ? file_put_contents($file, null)
            : file_put_contents($file, $pid . '|', 8);
    }

    protected function pidGet($file)
    {
        if (! ($data = file_get_contents($file)))
            return [];

        return array_filter(explode('|', $data));
    }

    public function monitorWorkersForLinux()
    {
        $this->debug('监听linux 信号');
        // 信号安装
        pcntl_signal(SIGUSR1, [$this, 'sigHandler'], false);
        while (1) {
            // Calls signal handlers for pending signals.
            \pcntl_signal_dispatch();
            // Suspends execution of the current process until a child has exited, or until a signal is delivered
            \pcntl_wait($status);
            // Calls signal handlers for pending signals again.
            \pcntl_signal_dispatch();
        }
    }

    public function sigHandler($sig)
    {
        switch ($sig) {
            case SIGUSR1:
                //重启
                $this->reload();
                break;

            //case SIGKILL: docker 中为 KILL
            case SIGKILL:
                // 停止
                $this->debug('杀死进程', $sig);
                $this->stop();
                break;
            default:
                $this->debug('难道走这里了？？？');
        }
    }

    public function send($conn, string $content)
    {
        $response = "HTTP/1.1 200 OK\r\n";
        $response .= "Content-Type: text/html;charset=UTF-8\r\n";
        $response .= "Connection: keep-alive\r\n";
        $response .= "Content-length: ".strlen($content)."\r\n\r\n";
        $response .= $content;
        fwrite($conn, $response);
    }

    protected function debug(string $str, $data = null)
    {
        echo $str . PHP_EOL;

        if ($data) {
            print_r($data);
            echo "\r\n";
        }
    }
}